/*
 * eap.c - Extensible Authentication Protocol for PPP (RFC 2284)
 *
 * Copyright (c) 2001 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 * Non-exclusive rights to redistribute, modify, translate, and use
 * this software in source and binary forms, in whole or in part, is
 * hereby granted, provided that the above copyright notice is
 * duplicated in any source form, and that neither the name of the
 * copyright holder nor the author is used to endorse or promote
 * products derived from this software.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Original version by James Carlson
 *
 * This implementation of EAP supports MD5-Challenge and SRP-SHA1
 * authentication styles.  Note that support of MD5-Challenge is a
 * requirement of RFC 2284, and that it's essentially just a
 * reimplementation of regular RFC 1994 CHAP using EAP messages.
 *
 * As an authenticator ("server"), there are multiple phases for each
 * style.  In the first phase of each style, the unauthenticated peer
 * name is queried using the EAP Identity request type.  If the
 * "remotename" option is used, then this phase is skipped, because
 * the peer's name is presumed to be known.
 *
 * For MD5-Challenge, there are two phases, and the second phase
 * consists of sending the challenge itself and handling the
 * associated response.
 *
 * For SRP-SHA1, there are four phases.  The second sends 's', 'N',
 * and 'g'.  The reply contains 'A'.  The third sends 'B', and the
 * reply contains 'M1'.  The forth sends the 'M2' value.
 *
 * As an authenticatee ("client"), there's just a single phase --
 * responding to the queries generated by the peer.  EAP is an
 * authenticator-driven protocol.
 *
 * Based on draft-ietf-pppext-eap-srp-03.txt.
 */

#include "netif/ppp/ppp_opts.h"
#if PPP_SUPPORT &&                                                             \
    EAP_SUPPORT /* don't build if not configured for use in lwipopts.h */

#include "netif/ppp/eap.h"
#include "netif/ppp/magic.h"
#include "netif/ppp/ppp_impl.h"
#include "netif/ppp/pppcrypt.h"

#ifdef USE_SRP
#include <t_client.h>
#include <t_pwd.h>
#include <t_server.h>
#endif /* USE_SRP */

#ifndef SHA_DIGESTSIZE
#define SHA_DIGESTSIZE 20
#endif

#ifdef USE_SRP
static char *pn_secret = NULL; /* Pseudonym generating secret */
#endif

#if PPP_OPTIONS
/*
 * Command-line options.
 */
static option_t eap_option_list[] = {
    {"eap-restart", o_int, &eap_states[0].es_server.ea_timeout,
     "Set retransmit timeout for EAP Requests (server)"},
    {"eap-max-sreq", o_int, &eap_states[0].es_server.ea_maxrequests,
     "Set max number of EAP Requests sent (server)"},
    {"eap-timeout", o_int, &eap_states[0].es_client.ea_timeout,
     "Set time limit for peer EAP authentication"},
    {"eap-max-rreq", o_int, &eap_states[0].es_client.ea_maxrequests,
     "Set max number of EAP Requests allows (client)"},
    {"eap-interval", o_int, &eap_states[0].es_rechallenge,
     "Set interval for EAP rechallenge"},
#ifdef USE_SRP
    {"srp-interval", o_int, &eap_states[0].es_lwrechallenge,
     "Set interval for SRP lightweight rechallenge"},
    {"srp-pn-secret", o_string, &pn_secret,
     "Long term pseudonym generation secret"},
    {"srp-use-pseudonym", o_bool, &eap_states[0].es_usepseudo,
     "Use pseudonym if offered one by server", 1},
#endif
    {NULL}};
#endif /* PPP_OPTIONS */

/*
 * Protocol entry points.
 */
static void eap_init(ppp_pcb *pcb);
static void eap_input(ppp_pcb *pcb, u_char *inp, int inlen);
static void eap_protrej(ppp_pcb *pcb);
static void eap_lowerup(ppp_pcb *pcb);
static void eap_lowerdown(ppp_pcb *pcb);
#if PRINTPKT_SUPPORT
static int eap_printpkt(const u_char *inp, int inlen,
                        void (*)(void *arg, const char *fmt, ...), void *arg);
#endif /* PRINTPKT_SUPPORT */

const struct protent eap_protent = {
    PPP_EAP,       /* protocol number */
    eap_init,      /* initialization procedure */
    eap_input,     /* process a received packet */
    eap_protrej,   /* process a received protocol-reject */
    eap_lowerup,   /* lower layer has gone up */
    eap_lowerdown, /* lower layer has gone down */
    NULL,          /* open the protocol */
    NULL,          /* close the protocol */
#if PRINTPKT_SUPPORT
    eap_printpkt, /* print a packet in readable form */
#endif            /* PRINTPKT_SUPPORT */
#if PPP_DATAINPUT
    NULL, /* process a received data packet */
#endif    /* PPP_DATAINPUT */
#if PRINTPKT_SUPPORT
    "EAP", /* text name of protocol */
    NULL,  /* text name of corresponding data protocol */
#endif     /* PRINTPKT_SUPPORT */
#if PPP_OPTIONS
    eap_option_list, /* list of command-line options */
    NULL,            /* check requested options; assign defaults */
#endif               /* PPP_OPTIONS */
#if DEMAND_SUPPORT
    NULL, /* configure interface for demand-dial */
    NULL  /* say whether to bring up link for this pkt */
#endif    /* DEMAND_SUPPORT */
};

#ifdef USE_SRP
/*
 * A well-known 2048 bit modulus.
 */
static const u_char wkmodulus[] = {
    0xAC, 0x6B, 0xDB, 0x41, 0x32, 0x4A, 0x9A, 0x9B, 0xF1, 0x66, 0xDE, 0x5E,
    0x13, 0x89, 0x58, 0x2F, 0xAF, 0x72, 0xB6, 0x65, 0x19, 0x87, 0xEE, 0x07,
    0xFC, 0x31, 0x92, 0x94, 0x3D, 0xB5, 0x60, 0x50, 0xA3, 0x73, 0x29, 0xCB,
    0xB4, 0xA0, 0x99, 0xED, 0x81, 0x93, 0xE0, 0x75, 0x77, 0x67, 0xA1, 0x3D,
    0xD5, 0x23, 0x12, 0xAB, 0x4B, 0x03, 0x31, 0x0D, 0xCD, 0x7F, 0x48, 0xA9,
    0xDA, 0x04, 0xFD, 0x50, 0xE8, 0x08, 0x39, 0x69, 0xED, 0xB7, 0x67, 0xB0,
    0xCF, 0x60, 0x95, 0x17, 0x9A, 0x16, 0x3A, 0xB3, 0x66, 0x1A, 0x05, 0xFB,
    0xD5, 0xFA, 0xAA, 0xE8, 0x29, 0x18, 0xA9, 0x96, 0x2F, 0x0B, 0x93, 0xB8,
    0x55, 0xF9, 0x79, 0x93, 0xEC, 0x97, 0x5E, 0xEA, 0xA8, 0x0D, 0x74, 0x0A,
    0xDB, 0xF4, 0xFF, 0x74, 0x73, 0x59, 0xD0, 0x41, 0xD5, 0xC3, 0x3E, 0xA7,
    0x1D, 0x28, 0x1E, 0x44, 0x6B, 0x14, 0x77, 0x3B, 0xCA, 0x97, 0xB4, 0x3A,
    0x23, 0xFB, 0x80, 0x16, 0x76, 0xBD, 0x20, 0x7A, 0x43, 0x6C, 0x64, 0x81,
    0xF1, 0xD2, 0xB9, 0x07, 0x87, 0x17, 0x46, 0x1A, 0x5B, 0x9D, 0x32, 0xE6,
    0x88, 0xF8, 0x77, 0x48, 0x54, 0x45, 0x23, 0xB5, 0x24, 0xB0, 0xD5, 0x7D,
    0x5E, 0xA7, 0x7A, 0x27, 0x75, 0xD2, 0xEC, 0xFA, 0x03, 0x2C, 0xFB, 0xDB,
    0xF5, 0x2F, 0xB3, 0x78, 0x61, 0x60, 0x27, 0x90, 0x04, 0xE5, 0x7A, 0xE6,
    0xAF, 0x87, 0x4E, 0x73, 0x03, 0xCE, 0x53, 0x29, 0x9C, 0xCC, 0x04, 0x1C,
    0x7B, 0xC3, 0x08, 0xD8, 0x2A, 0x56, 0x98, 0xF3, 0xA8, 0xD0, 0xC3, 0x82,
    0x71, 0xAE, 0x35, 0xF8, 0xE9, 0xDB, 0xFB, 0xB6, 0x94, 0xB5, 0xC8, 0x03,
    0xD8, 0x9F, 0x7A, 0xE4, 0x35, 0xDE, 0x23, 0x6D, 0x52, 0x5F, 0x54, 0x75,
    0x9B, 0x65, 0xE3, 0x72, 0xFC, 0xD6, 0x8E, 0xF2, 0x0F, 0xA7, 0x11, 0x1F,
    0x9E, 0x4A, 0xFF, 0x73};
#endif

#if PPP_SERVER
/* Local forward declarations. */
static void eap_server_timeout(void *arg);
#endif /* PPP_SERVER */

/*
 * Convert EAP state code to printable string for debug.
 */
static const char *eap_state_name(enum eap_state_code esc) {
  static const char *state_names[] = {EAP_STATES};

  return (state_names[(int)esc]);
}

/*
 * eap_init - Initialize state for an EAP user.  This is currently
 * called once by main() during start-up.
 */
static void eap_init(ppp_pcb *pcb) {

  BZERO(&pcb->eap, sizeof(eap_state));
#if PPP_SERVER
  pcb->eap.es_server.ea_id = magic();
#endif /* PPP_SERVER */
}

/*
 * eap_client_timeout - Give up waiting for the peer to send any
 * Request messages.
 */
static void eap_client_timeout(void *arg) {
  ppp_pcb *pcb = (ppp_pcb *)arg;

  if (!eap_client_active(pcb))
    return;

  ppp_error(("EAP: timeout waiting for Request from peer"));
  auth_withpeer_fail(pcb, PPP_EAP);
  pcb->eap.es_client.ea_state = eapBadAuth;
}

/*
 * eap_authwithpeer - Authenticate to our peer (behave as client).
 *
 * Start client state and wait for requests.  This is called only
 * after eap_lowerup.
 */
void eap_authwithpeer(ppp_pcb *pcb, const char *localname) {

  if (NULL == localname)
    return;

  /* Save the peer name we're given */
  pcb->eap.es_client.ea_name = localname;
  pcb->eap.es_client.ea_namelen = strlen(localname);

  pcb->eap.es_client.ea_state = eapListen;

  /*
   * Start a timer so that if the other end just goes
   * silent, we don't sit here waiting forever.
   */
  if (pcb->settings.eap_req_time > 0)
    TIMEOUT(eap_client_timeout, pcb, pcb->settings.eap_req_time);
}

#if PPP_SERVER
/*
 * Format a standard EAP Failure message and send it to the peer.
 * (Server operation)
 */
static void eap_send_failure(ppp_pcb *pcb) {
  struct pbuf *p;
  u_char *outp;

  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + EAP_HEADERLEN), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_FAILURE, outp);
  pcb->eap.es_server.ea_id++;
  PUTCHAR(pcb->eap.es_server.ea_id, outp);
  PUTSHORT(EAP_HEADERLEN, outp);

  ppp_write(pcb, p);

  pcb->eap.es_server.ea_state = eapBadAuth;
  auth_peer_fail(pcb, PPP_EAP);
}

/*
 * Format a standard EAP Success message and send it to the peer.
 * (Server operation)
 */
static void eap_send_success(ppp_pcb *pcb) {
  struct pbuf *p;
  u_char *outp;

  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + EAP_HEADERLEN), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_SUCCESS, outp);
  pcb->eap.es_server.ea_id++;
  PUTCHAR(pcb->eap.es_server.ea_id, outp);
  PUTSHORT(EAP_HEADERLEN, outp);

  ppp_write(pcb, p);

  auth_peer_success(pcb, PPP_EAP, 0, pcb->eap.es_server.ea_peer,
                    pcb->eap.es_server.ea_peerlen);
}
#endif /* PPP_SERVER */

#ifdef USE_SRP
/*
 * Set DES key according to pseudonym-generating secret and current
 * date.
 */
static bool pncrypt_setkey(int timeoffs) {
  struct tm *tp;
  char tbuf[9];
  SHA1_CTX ctxt;
  u_char dig[SHA_DIGESTSIZE];
  time_t reftime;

  if (pn_secret == NULL)
    return (0);
  reftime = time(NULL) + timeoffs;
  tp = localtime(&reftime);
  SHA1Init(&ctxt);
  SHA1Update(&ctxt, pn_secret, strlen(pn_secret));
  strftime(tbuf, sizeof(tbuf), "%Y%m%d", tp);
  SHA1Update(&ctxt, tbuf, strlen(tbuf));
  SHA1Final(dig, &ctxt);
  /* FIXME: if we want to do SRP, we need to find a way to pass the PolarSSL
   * des_context instead of using static memory */
  return (DesSetkey(dig));
}

static char base64[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

struct b64state {
  u32_t bs_bits;
  int bs_offs;
};

static int b64enc(bs, inp, inlen, outp)
struct b64state *bs;
u_char *inp;
int inlen;
u_char *outp;
{
  int outlen = 0;

  while (inlen > 0) {
    bs->bs_bits = (bs->bs_bits << 8) | *inp++;
    inlen--;
    bs->bs_offs += 8;
    if (bs->bs_offs >= 24) {
      *outp++ = base64[(bs->bs_bits >> 18) & 0x3F];
      *outp++ = base64[(bs->bs_bits >> 12) & 0x3F];
      *outp++ = base64[(bs->bs_bits >> 6) & 0x3F];
      *outp++ = base64[bs->bs_bits & 0x3F];
      outlen += 4;
      bs->bs_offs = 0;
      bs->bs_bits = 0;
    }
  }
  return (outlen);
}

static int b64flush(bs, outp)
struct b64state *bs;
u_char *outp;
{
  int outlen = 0;

  if (bs->bs_offs == 8) {
    *outp++ = base64[(bs->bs_bits >> 2) & 0x3F];
    *outp++ = base64[(bs->bs_bits << 4) & 0x3F];
    outlen = 2;
  } else if (bs->bs_offs == 16) {
    *outp++ = base64[(bs->bs_bits >> 10) & 0x3F];
    *outp++ = base64[(bs->bs_bits >> 4) & 0x3F];
    *outp++ = base64[(bs->bs_bits << 2) & 0x3F];
    outlen = 3;
  }
  bs->bs_offs = 0;
  bs->bs_bits = 0;
  return (outlen);
}

static int b64dec(bs, inp, inlen, outp)
struct b64state *bs;
u_char *inp;
int inlen;
u_char *outp;
{
  int outlen = 0;
  char *cp;

  while (inlen > 0) {
    if ((cp = strchr(base64, *inp++)) == NULL)
      break;
    bs->bs_bits = (bs->bs_bits << 6) | (cp - base64);
    inlen--;
    bs->bs_offs += 6;
    if (bs->bs_offs >= 8) {
      *outp++ = bs->bs_bits >> (bs->bs_offs - 8);
      outlen++;
      bs->bs_offs -= 8;
    }
  }
  return (outlen);
}
#endif /* USE_SRP */

#if PPP_SERVER
/*
 * Assume that current waiting server state is complete and figure
 * next state to use based on available authentication data.  'status'
 * indicates if there was an error in handling the last query.  It is
 * 0 for success and non-zero for failure.
 */
static void eap_figure_next_state(ppp_pcb *pcb, int status) {
#ifdef USE_SRP
  unsigned char secbuf[MAXSECRETLEN], clear[8], *sp, *dp;
  struct t_pw tpw;
  struct t_confent *tce, mytce;
  char *cp, *cp2;
  struct t_server *ts;
  int id, i, plen, toffs;
  u_char vals[2];
  struct b64state bs;
#endif /* USE_SRP */

  pcb->settings.eap_timeout_time = pcb->eap.es_savedtime;
  switch (pcb->eap.es_server.ea_state) {
  case eapBadAuth:
    return;

  case eapIdentify:
#ifdef USE_SRP
    /* Discard any previous session. */
    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    if (ts != NULL) {
      t_serverclose(ts);
      pcb->eap.es_server.ea_session = NULL;
      pcb->eap.es_server.ea_skey = NULL;
    }
#endif /* USE_SRP */
    if (status != 0) {
      pcb->eap.es_server.ea_state = eapBadAuth;
      break;
    }
#ifdef USE_SRP
    /* If we've got a pseudonym, try to decode to real name. */
    if (pcb->eap.es_server.ea_peerlen > SRP_PSEUDO_LEN &&
        strncmp(pcb->eap.es_server.ea_peer, SRP_PSEUDO_ID, SRP_PSEUDO_LEN) ==
            0 &&
        (pcb->eap.es_server.ea_peerlen - SRP_PSEUDO_LEN) * 3 / 4 <
            sizeof(secbuf)) {
      BZERO(&bs, sizeof(bs));
      plen = b64dec(&bs, pcb->eap.es_server.ea_peer + SRP_PSEUDO_LEN,
                    pcb->eap.es_server.ea_peerlen - SRP_PSEUDO_LEN, secbuf);
      toffs = 0;
      for (i = 0; i < 5; i++) {
        pncrypt_setkey(toffs);
        toffs -= 86400;
        /* FIXME: if we want to do SRP, we need to find a way to pass the
         * PolarSSL des_context instead of using static memory */
        if (!DesDecrypt(secbuf, clear)) {
          ppp_dbglog(("no DES here; cannot decode "
                      "pseudonym"));
          return;
        }
        id = *(unsigned char *)clear;
        if (id + 1 <= plen && id + 9 > plen)
          break;
      }
      if (plen % 8 == 0 && i < 5) {
        /*
         * Note that this is always shorter than the
         * original stored string, so there's no need
         * to realloc.
         */
        if ((i = plen = *(unsigned char *)clear) > 7)
          i = 7;
        pcb->eap.es_server.ea_peerlen = plen;
        dp = (unsigned char *)pcb->eap.es_server.ea_peer;
        MEMCPY(dp, clear + 1, i);
        plen -= i;
        dp += i;
        sp = secbuf + 8;
        while (plen > 0) {
          /* FIXME: if we want to do SRP, we need to find a way to pass the
           * PolarSSL des_context instead of using static memory */
          (void)DesDecrypt(sp, dp);
          sp += 8;
          dp += 8;
          plen -= 8;
        }
        pcb->eap.es_server.ea_peer[pcb->eap.es_server.ea_peerlen] = '\0';
        ppp_dbglog(("decoded pseudonym to \"%.*q\"",
                    pcb->eap.es_server.ea_peerlen, pcb->eap.es_server.ea_peer));
      } else {
        ppp_dbglog(("failed to decode real name"));
        /* Stay in eapIdentfy state; requery */
        break;
      }
    }
    /* Look up user in secrets database. */
    if (get_srp_secret(pcb->eap.es_unit, pcb->eap.es_server.ea_peer,
                       pcb->eap.es_server.ea_name, (char *)secbuf, 1) != 0) {
      /* Set up default in case SRP entry is bad */
      pcb->eap.es_server.ea_state = eapMD5Chall;
      /* Get t_confent based on index in srp-secrets */
      id = strtol((char *)secbuf, &cp, 10);
      if (*cp++ != ':' || id < 0)
        break;
      if (id == 0) {
        mytce.index = 0;
        mytce.modulus.data = (u_char *)wkmodulus;
        mytce.modulus.len = sizeof(wkmodulus);
        mytce.generator.data = (u_char *)"\002";
        mytce.generator.len = 1;
        tce = &mytce;
      } else if ((tce = gettcid(id)) != NULL) {
        /*
         * Client will have to verify this modulus/
         * generator combination, and that will take
         * a while.  Lengthen the timeout here.
         */
        if (pcb->settings.eap_timeout_time > 0 &&
            pcb->settings.eap_timeout_time < 30)
          pcb->settings.eap_timeout_time = 30;
      } else {
        break;
      }
      if ((cp2 = strchr(cp, ':')) == NULL)
        break;
      *cp2++ = '\0';
      tpw.pebuf.name = pcb->eap.es_server.ea_peer;
      tpw.pebuf.password.len = t_fromb64((char *)tpw.pwbuf, cp);
      tpw.pebuf.password.data = tpw.pwbuf;
      tpw.pebuf.salt.len = t_fromb64((char *)tpw.saltbuf, cp2);
      tpw.pebuf.salt.data = tpw.saltbuf;
      if ((ts = t_serveropenraw(&tpw.pebuf, tce)) == NULL)
        break;
      pcb->eap.es_server.ea_session = (void *)ts;
      pcb->eap.es_server.ea_state = eapSRP1;
      vals[0] = pcb->eap.es_server.ea_id + 1;
      vals[1] = EAPT_SRP;
      t_serveraddexdata(ts, vals, 2);
      /* Generate B; must call before t_servergetkey() */
      t_servergenexp(ts);
      break;
    }
#endif /* USE_SRP */
    pcb->eap.es_server.ea_state = eapMD5Chall;
    break;

  case eapSRP1:
#ifdef USE_SRP
    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    if (ts != NULL && status != 0) {
      t_serverclose(ts);
      pcb->eap.es_server.ea_session = NULL;
      pcb->eap.es_server.ea_skey = NULL;
    }
#endif /* USE_SRP */
    if (status == 1) {
      pcb->eap.es_server.ea_state = eapMD5Chall;
    } else if (status != 0 || pcb->eap.es_server.ea_session == NULL) {
      pcb->eap.es_server.ea_state = eapBadAuth;
    } else {
      pcb->eap.es_server.ea_state = eapSRP2;
    }
    break;

  case eapSRP2:
#ifdef USE_SRP
    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    if (ts != NULL && status != 0) {
      t_serverclose(ts);
      pcb->eap.es_server.ea_session = NULL;
      pcb->eap.es_server.ea_skey = NULL;
    }
#endif /* USE_SRP */
    if (status != 0 || pcb->eap.es_server.ea_session == NULL) {
      pcb->eap.es_server.ea_state = eapBadAuth;
    } else {
      pcb->eap.es_server.ea_state = eapSRP3;
    }
    break;

  case eapSRP3:
  case eapSRP4:
#ifdef USE_SRP
    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    if (ts != NULL && status != 0) {
      t_serverclose(ts);
      pcb->eap.es_server.ea_session = NULL;
      pcb->eap.es_server.ea_skey = NULL;
    }
#endif /* USE_SRP */
    if (status != 0 || pcb->eap.es_server.ea_session == NULL) {
      pcb->eap.es_server.ea_state = eapBadAuth;
    } else {
      pcb->eap.es_server.ea_state = eapOpen;
    }
    break;

  case eapMD5Chall:
    if (status != 0) {
      pcb->eap.es_server.ea_state = eapBadAuth;
    } else {
      pcb->eap.es_server.ea_state = eapOpen;
    }
    break;

  default:
    pcb->eap.es_server.ea_state = eapBadAuth;
    break;
  }
  if (pcb->eap.es_server.ea_state == eapBadAuth)
    eap_send_failure(pcb);
}

/*
 * Format an EAP Request message and send it to the peer.  Message
 * type depends on current state.  (Server operation)
 */
static void eap_send_request(ppp_pcb *pcb) {
  struct pbuf *p;
  u_char *outp;
  u_char *lenloc;
  int outlen;
  int len;
  const char *str;
#ifdef USE_SRP
  struct t_server *ts;
  u_char clear[8], cipher[8], dig[SHA_DIGESTSIZE], *optr, *cp;
  int i, j;
  struct b64state b64;
  SHA1_CTX ctxt;
#endif /* USE_SRP */

  /* Handle both initial auth and restart */
  if (pcb->eap.es_server.ea_state < eapIdentify &&
      pcb->eap.es_server.ea_state != eapInitial) {
    pcb->eap.es_server.ea_state = eapIdentify;
#if PPP_REMOTENAME
    if (pcb->settings.explicit_remote && pcb->remote_name) {
      /*
       * If we already know the peer's
       * unauthenticated name, then there's no
       * reason to ask.  Go to next state instead.
       */
      int len = (int)strlen(pcb->remote_name);
      if (len > MAXNAMELEN) {
        len = MAXNAMELEN;
      }
      MEMCPY(pcb->eap.es_server.ea_peer, pcb->remote_name, len);
      pcb->eap.es_server.ea_peer[len] = '\0';
      pcb->eap.es_server.ea_peerlen = len;
      eap_figure_next_state(pcb, 0);
    }
#endif /* PPP_REMOTENAME */
  }

  if (pcb->settings.eap_max_transmits > 0 &&
      pcb->eap.es_server.ea_requests >= pcb->settings.eap_max_transmits) {
    if (pcb->eap.es_server.ea_responses > 0)
      ppp_error(("EAP: too many Requests sent"));
    else
      ppp_error(("EAP: no response to Requests"));
    eap_send_failure(pcb);
    return;
  }

  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_CTRL_PBUF_UNKNOWN_SIZE), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_REQUEST, outp);
  PUTCHAR(pcb->eap.es_server.ea_id, outp);
  lenloc = outp;
  INCPTR(2, outp);

  switch (pcb->eap.es_server.ea_state) {
  case eapIdentify:
    PUTCHAR(EAPT_IDENTITY, outp);
    str = "Name";
    len = strlen(str);
    MEMCPY(outp, str, len);
    INCPTR(len, outp);
    break;

  case eapMD5Chall:
    PUTCHAR(EAPT_MD5CHAP, outp);
    /*
     * pick a random challenge length between
     * EAP_MIN_CHALLENGE_LENGTH and EAP_MAX_CHALLENGE_LENGTH
     */
    pcb->eap.es_challen = EAP_MIN_CHALLENGE_LENGTH +
                          magic_pow(EAP_MIN_MAX_POWER_OF_TWO_CHALLENGE_LENGTH);
    PUTCHAR(pcb->eap.es_challen, outp);
    magic_random_bytes(pcb->eap.es_challenge, pcb->eap.es_challen);
    MEMCPY(outp, pcb->eap.es_challenge, pcb->eap.es_challen);
    INCPTR(pcb->eap.es_challen, outp);
    MEMCPY(outp, pcb->eap.es_server.ea_name, pcb->eap.es_server.ea_namelen);
    INCPTR(pcb->eap.es_server.ea_namelen, outp);
    break;

#ifdef USE_SRP
  case eapSRP1:
    PUTCHAR(EAPT_SRP, outp);
    PUTCHAR(EAPSRP_CHALLENGE, outp);

    PUTCHAR(pcb->eap.es_server.ea_namelen, outp);
    MEMCPY(outp, pcb->eap.es_server.ea_name, pcb->eap.es_server.ea_namelen);
    INCPTR(pcb->eap.es_server.ea_namelen, outp);

    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    assert(ts != NULL);
    PUTCHAR(ts->s.len, outp);
    MEMCPY(outp, ts->s.data, ts->s.len);
    INCPTR(ts->s.len, outp);

    if (ts->g.len == 1 && ts->g.data[0] == 2) {
      PUTCHAR(0, outp);
    } else {
      PUTCHAR(ts->g.len, outp);
      MEMCPY(outp, ts->g.data, ts->g.len);
      INCPTR(ts->g.len, outp);
    }

    if (ts->n.len != sizeof(wkmodulus) ||
        BCMP(ts->n.data, wkmodulus, sizeof(wkmodulus)) != 0) {
      MEMCPY(outp, ts->n.data, ts->n.len);
      INCPTR(ts->n.len, outp);
    }
    break;

  case eapSRP2:
    PUTCHAR(EAPT_SRP, outp);
    PUTCHAR(EAPSRP_SKEY, outp);

    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    assert(ts != NULL);
    MEMCPY(outp, ts->B.data, ts->B.len);
    INCPTR(ts->B.len, outp);
    break;

  case eapSRP3:
    PUTCHAR(EAPT_SRP, outp);
    PUTCHAR(EAPSRP_SVALIDATOR, outp);
    PUTLONG(SRPVAL_EBIT, outp);
    ts = (struct t_server *)pcb->eap.es_server.ea_session;
    assert(ts != NULL);
    MEMCPY(outp, t_serverresponse(ts), SHA_DIGESTSIZE);
    INCPTR(SHA_DIGESTSIZE, outp);

    if (pncrypt_setkey(0)) {
      /* Generate pseudonym */
      optr = outp;
      cp = (unsigned char *)pcb->eap.es_server.ea_peer;
      if ((j = i = pcb->eap.es_server.ea_peerlen) > 7)
        j = 7;
      clear[0] = i;
      MEMCPY(clear + 1, cp, j);
      i -= j;
      cp += j;
      /* FIXME: if we want to do SRP, we need to find a way to pass the PolarSSL
       * des_context instead of using static memory */
      if (!DesEncrypt(clear, cipher)) {
        ppp_dbglog(("no DES here; not generating pseudonym"));
        break;
      }
      BZERO(&b64, sizeof(b64));
      outp++; /* space for pseudonym length */
      outp += b64enc(&b64, cipher, 8, outp);
      while (i >= 8) {
        /* FIXME: if we want to do SRP, we need to find a way to pass the
         * PolarSSL des_context instead of using static memory */
        (void)DesEncrypt(cp, cipher);
        outp += b64enc(&b64, cipher, 8, outp);
        cp += 8;
        i -= 8;
      }
      if (i > 0) {
        MEMCPY(clear, cp, i);
        cp += i;
        magic_random_bytes(cp, 8 - i);
        /* FIXME: if we want to do SRP, we need to find a way to pass the
         * PolarSSL des_context instead of using static memory */
        (void)DesEncrypt(clear, cipher);
        outp += b64enc(&b64, cipher, 8, outp);
      }
      outp += b64flush(&b64, outp);

      /* Set length and pad out to next 20 octet boundary */
      i = outp - optr - 1;
      *optr = i;
      i %= SHA_DIGESTSIZE;
      if (i != 0) {
        magic_random_bytes(outp, SHA_DIGESTSIZE - i);
        INCPTR(SHA_DIGESTSIZE - i, outp);
      }

      /* Obscure the pseudonym with SHA1 hash */
      SHA1Init(&ctxt);
      SHA1Update(&ctxt, &pcb->eap.es_server.ea_id, 1);
      SHA1Update(&ctxt, pcb->eap.es_server.ea_skey, SESSION_KEY_LEN);
      SHA1Update(&ctxt, pcb->eap.es_server.ea_peer,
                 pcb->eap.es_server.ea_peerlen);
      while (optr < outp) {
        SHA1Final(dig, &ctxt);
        cp = dig;
        while (cp < dig + SHA_DIGESTSIZE)
          *optr++ ^= *cp++;
        SHA1Init(&ctxt);
        SHA1Update(&ctxt, &pcb->eap.es_server.ea_id, 1);
        SHA1Update(&ctxt, pcb->eap.es_server.ea_skey, SESSION_KEY_LEN);
        SHA1Update(&ctxt, optr - SHA_DIGESTSIZE, SHA_DIGESTSIZE);
      }
    }
    break;

  case eapSRP4:
    PUTCHAR(EAPT_SRP, outp);
    PUTCHAR(EAPSRP_LWRECHALLENGE, outp);
    pcb->eap.es_challen = EAP_MIN_CHALLENGE_LENGTH +
                          magic_pow(EAP_MIN_MAX_POWER_OF_TWO_CHALLENGE_LENGTH);
    magic_random_bytes(pcb->eap.es_challenge, pcb->eap.es_challen);
    MEMCPY(outp, pcb->eap.es_challenge, pcb->eap.es_challen);
    INCPTR(pcb->eap.es_challen, outp);
    break;
#endif /* USE_SRP */

  default:
    return;
  }

  outlen = (outp - (unsigned char *)p->payload) - PPP_HDRLEN;
  PUTSHORT(outlen, lenloc);

  pbuf_realloc(p, outlen + PPP_HDRLEN);
  ppp_write(pcb, p);

  pcb->eap.es_server.ea_requests++;

  if (pcb->settings.eap_timeout_time > 0)
    TIMEOUT(eap_server_timeout, pcb, pcb->settings.eap_timeout_time);
}

/*
 * eap_authpeer - Authenticate our peer (behave as server).
 *
 * Start server state and send first request.  This is called only
 * after eap_lowerup.
 */
void eap_authpeer(ppp_pcb *pcb, const char *localname) {

  /* Save the name we're given. */
  pcb->eap.es_server.ea_name = localname;
  pcb->eap.es_server.ea_namelen = strlen(localname);

  pcb->eap.es_savedtime = pcb->settings.eap_timeout_time;

  /* Lower layer up yet? */
  if (pcb->eap.es_server.ea_state == eapInitial ||
      pcb->eap.es_server.ea_state == eapPending) {
    pcb->eap.es_server.ea_state = eapPending;
    return;
  }

  pcb->eap.es_server.ea_state = eapPending;

  /* ID number not updated here intentionally; hashed into M1 */
  eap_send_request(pcb);
}

/*
 * eap_server_timeout - Retransmission timer for sending Requests
 * expired.
 */
static void eap_server_timeout(void *arg) {
  ppp_pcb *pcb = (ppp_pcb *)arg;

  if (!eap_server_active(pcb))
    return;

  /* EAP ID number must not change on timeout. */
  eap_send_request(pcb);
}

/*
 * When it's time to send rechallenge the peer, this timeout is
 * called.  Once the rechallenge is successful, the response handler
 * will restart the timer.  If it fails, then the link is dropped.
 */
static void eap_rechallenge(void *arg) {
  ppp_pcb *pcb = (ppp_pcb *)arg;

  if (pcb->eap.es_server.ea_state != eapOpen &&
      pcb->eap.es_server.ea_state != eapSRP4)
    return;

  pcb->eap.es_server.ea_requests = 0;
  pcb->eap.es_server.ea_state = eapIdentify;
  eap_figure_next_state(pcb, 0);
  pcb->eap.es_server.ea_id++;
  eap_send_request(pcb);
}

static void srp_lwrechallenge(void *arg) {
  ppp_pcb *pcb = (ppp_pcb *)arg;

  if (pcb->eap.es_server.ea_state != eapOpen ||
      pcb->eap.es_server.ea_type != EAPT_SRP)
    return;

  pcb->eap.es_server.ea_requests = 0;
  pcb->eap.es_server.ea_state = eapSRP4;
  pcb->eap.es_server.ea_id++;
  eap_send_request(pcb);
}
#endif /* PPP_SERVER */

/*
 * eap_lowerup - The lower layer is now up.
 *
 * This is called before either eap_authpeer or eap_authwithpeer.  See
 * link_established() in auth.c.  All that's necessary here is to
 * return to closed state so that those two routines will do the right
 * thing.
 */
static void eap_lowerup(ppp_pcb *pcb) {
  pcb->eap.es_client.ea_state = eapClosed;
#if PPP_SERVER
  pcb->eap.es_server.ea_state = eapClosed;
#endif /* PPP_SERVER */
}

/*
 * eap_lowerdown - The lower layer is now down.
 *
 * Cancel all timeouts and return to initial state.
 */
static void eap_lowerdown(ppp_pcb *pcb) {

  if (eap_client_active(pcb) && pcb->settings.eap_req_time > 0) {
    UNTIMEOUT(eap_client_timeout, pcb);
  }
#if PPP_SERVER
  if (eap_server_active(pcb)) {
    if (pcb->settings.eap_timeout_time > 0) {
      UNTIMEOUT(eap_server_timeout, pcb);
    }
  } else {
    if ((pcb->eap.es_server.ea_state == eapOpen ||
         pcb->eap.es_server.ea_state == eapSRP4) &&
        pcb->eap.es_rechallenge > 0) {
      UNTIMEOUT(eap_rechallenge, (void *)pcb);
    }
    if (pcb->eap.es_server.ea_state == eapOpen &&
        pcb->eap.es_lwrechallenge > 0) {
      UNTIMEOUT(srp_lwrechallenge, (void *)pcb);
    }
  }

  pcb->eap.es_client.ea_state = pcb->eap.es_server.ea_state = eapInitial;
  pcb->eap.es_client.ea_requests = pcb->eap.es_server.ea_requests = 0;
#endif /* PPP_SERVER */
}

/*
 * eap_protrej - Peer doesn't speak this protocol.
 *
 * This shouldn't happen.  If it does, it represents authentication
 * failure.
 */
static void eap_protrej(ppp_pcb *pcb) {

  if (eap_client_active(pcb)) {
    ppp_error(("EAP authentication failed due to Protocol-Reject"));
    auth_withpeer_fail(pcb, PPP_EAP);
  }
#if PPP_SERVER
  if (eap_server_active(pcb)) {
    ppp_error(("EAP authentication of peer failed on Protocol-Reject"));
    auth_peer_fail(pcb, PPP_EAP);
  }
#endif /* PPP_SERVER */
  eap_lowerdown(pcb);
}

/*
 * Format and send a regular EAP Response message.
 */
static void eap_send_response(ppp_pcb *pcb, u_char id, u_char typenum,
                              const u_char *str, int lenstr) {
  struct pbuf *p;
  u_char *outp;
  int msglen;

  msglen = EAP_HEADERLEN + sizeof(u_char) + lenstr;
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + msglen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_RESPONSE, outp);
  PUTCHAR(id, outp);
  pcb->eap.es_client.ea_id = id;
  PUTSHORT(msglen, outp);
  PUTCHAR(typenum, outp);
  if (lenstr > 0) {
    MEMCPY(outp, str, lenstr);
  }

  ppp_write(pcb, p);
}

/*
 * Format and send an MD5-Challenge EAP Response message.
 */
static void eap_chap_response(ppp_pcb *pcb, u_char id, u_char *hash,
                              const char *name, int namelen) {
  struct pbuf *p;
  u_char *outp;
  int msglen;

  msglen = EAP_HEADERLEN + 2 * sizeof(u_char) + MD5_SIGNATURE_SIZE + namelen;
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + msglen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_RESPONSE, outp);
  PUTCHAR(id, outp);
  pcb->eap.es_client.ea_id = id;
  PUTSHORT(msglen, outp);
  PUTCHAR(EAPT_MD5CHAP, outp);
  PUTCHAR(MD5_SIGNATURE_SIZE, outp);
  MEMCPY(outp, hash, MD5_SIGNATURE_SIZE);
  INCPTR(MD5_SIGNATURE_SIZE, outp);
  if (namelen > 0) {
    MEMCPY(outp, name, namelen);
  }

  ppp_write(pcb, p);
}

#ifdef USE_SRP
/*
 * Format and send a SRP EAP Response message.
 */
static void eap_srp_response(esp, id, subtypenum, str, lenstr) eap_state *esp;
u_char id;
u_char subtypenum;
u_char *str;
int lenstr;
{
  ppp_pcb *pcb = &ppp_pcb_list[pcb->eap.es_unit];
  struct pbuf *p;
  u_char *outp;
  int msglen;

  msglen = EAP_HEADERLEN + 2 * sizeof(u_char) + lenstr;
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + msglen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_RESPONSE, outp);
  PUTCHAR(id, outp);
  pcb->eap.es_client.ea_id = id;
  PUTSHORT(msglen, outp);
  PUTCHAR(EAPT_SRP, outp);
  PUTCHAR(subtypenum, outp);
  if (lenstr > 0) {
    MEMCPY(outp, str, lenstr);
  }

  ppp_write(pcb, p);
}

/*
 * Format and send a SRP EAP Client Validator Response message.
 */
static void eap_srpval_response(esp, id, flags, str) eap_state *esp;
u_char id;
u32_t flags;
u_char *str;
{
  ppp_pcb *pcb = &ppp_pcb_list[pcb->eap.es_unit];
  struct pbuf *p;
  u_char *outp;
  int msglen;

  msglen = EAP_HEADERLEN + 2 * sizeof(u_char) + sizeof(u32_t) + SHA_DIGESTSIZE;
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + msglen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_RESPONSE, outp);
  PUTCHAR(id, outp);
  pcb->eap.es_client.ea_id = id;
  PUTSHORT(msglen, outp);
  PUTCHAR(EAPT_SRP, outp);
  PUTCHAR(EAPSRP_CVALIDATOR, outp);
  PUTLONG(flags, outp);
  MEMCPY(outp, str, SHA_DIGESTSIZE);

  ppp_write(pcb, p);
}
#endif /* USE_SRP */

static void eap_send_nak(ppp_pcb *pcb, u_char id, u_char type) {
  struct pbuf *p;
  u_char *outp;
  int msglen;

  msglen = EAP_HEADERLEN + 2 * sizeof(u_char);
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + msglen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;

  MAKEHEADER(outp, PPP_EAP);

  PUTCHAR(EAP_RESPONSE, outp);
  PUTCHAR(id, outp);
  pcb->eap.es_client.ea_id = id;
  PUTSHORT(msglen, outp);
  PUTCHAR(EAPT_NAK, outp);
  PUTCHAR(type, outp);

  ppp_write(pcb, p);
}

#ifdef USE_SRP
static char *name_of_pn_file() {
  char *user, *path, *file;
  struct passwd *pw;
  size_t pl;
  static bool pnlogged = 0;

  pw = getpwuid(getuid());
  if (pw == NULL || (user = pw->pw_dir) == NULL || user[0] == 0) {
    errno = EINVAL;
    return (NULL);
  }
  file = _PATH_PSEUDONYM;
  pl = strlen(user) + strlen(file) + 2;
  path = malloc(pl);
  if (path == NULL)
    return (NULL);
  (void)slprintf(path, pl, "%s/%s", user, file);
  if (!pnlogged) {
    ppp_dbglog(("pseudonym file: %s", path));
    pnlogged = 1;
  }
  return (path);
}

static int open_pn_file(modebits)
mode_t modebits;
{
  char *path;
  int fd, err;

  if ((path = name_of_pn_file()) == NULL)
    return (-1);
  fd = open(path, modebits, S_IRUSR | S_IWUSR);
  err = errno;
  free(path);
  errno = err;
  return (fd);
}

static void remove_pn_file() {
  char *path;

  if ((path = name_of_pn_file()) != NULL) {
    (void)unlink(path);
    (void)free(path);
  }
}

static void write_pseudonym(esp, inp, len, id) eap_state *esp;
u_char *inp;
int len, id;
{
  u_char val;
  u_char *datp, *digp;
  SHA1_CTX ctxt;
  u_char dig[SHA_DIGESTSIZE];
  int dsize, fd, olen = len;

  /*
   * Do the decoding by working backwards.  This eliminates the need
   * to save the decoded output in a separate buffer.
   */
  val = id;
  while (len > 0) {
    if ((dsize = len % SHA_DIGESTSIZE) == 0)
      dsize = SHA_DIGESTSIZE;
    len -= dsize;
    datp = inp + len;
    SHA1Init(&ctxt);
    SHA1Update(&ctxt, &val, 1);
    SHA1Update(&ctxt, pcb->eap.es_client.ea_skey, SESSION_KEY_LEN);
    if (len > 0) {
      SHA1Update(&ctxt, datp, SHA_DIGESTSIZE);
    } else {
      SHA1Update(&ctxt, pcb->eap.es_client.ea_name,
                 pcb->eap.es_client.ea_namelen);
    }
    SHA1Final(dig, &ctxt);
    for (digp = dig; digp < dig + SHA_DIGESTSIZE; digp++)
      *datp++ ^= *digp;
  }

  /* Now check that the result is sane */
  if (olen <= 0 || *inp + 1 > olen) {
    ppp_dbglog(("EAP: decoded pseudonym is unusable <%.*B>", olen, inp));
    return;
  }

  /* Save it away */
  fd = open_pn_file(O_WRONLY | O_CREAT | O_TRUNC);
  if (fd < 0) {
    ppp_dbglog(("EAP: error saving pseudonym: %m"));
    return;
  }
  len = write(fd, inp + 1, *inp);
  if (close(fd) != -1 && len == *inp) {
    ppp_dbglog(("EAP: saved pseudonym"));
    pcb->eap.es_usedpseudo = 0;
  } else {
    ppp_dbglog(("EAP: failed to save pseudonym"));
    remove_pn_file();
  }
}
#endif /* USE_SRP */

/*
 * eap_request - Receive EAP Request message (client mode).
 */
static void eap_request(ppp_pcb *pcb, u_char *inp, int id, int len) {
  u_char typenum;
  u_char vallen;
  int secret_len;
  char secret[MAXSECRETLEN];
  char rhostname[MAXNAMELEN];
  lwip_md5_context mdContext;
  u_char hash[MD5_SIGNATURE_SIZE];
#ifdef USE_SRP
  struct t_client *tc;
  struct t_num sval, gval, Nval, *Ap, Bval;
  u_char vals[2];
  SHA1_CTX ctxt;
  u_char dig[SHA_DIGESTSIZE];
  int fd;
#endif /* USE_SRP */

  /*
   * Ignore requests if we're not open
   */
  if (pcb->eap.es_client.ea_state <= eapClosed)
    return;

  /*
   * Note: we update es_client.ea_id *only if* a Response
   * message is being generated.  Otherwise, we leave it the
   * same for duplicate detection purposes.
   */

  pcb->eap.es_client.ea_requests++;
  if (pcb->settings.eap_allow_req != 0 &&
      pcb->eap.es_client.ea_requests > pcb->settings.eap_allow_req) {
    ppp_info(("EAP: received too many Request messages"));
    if (pcb->settings.eap_req_time > 0) {
      UNTIMEOUT(eap_client_timeout, pcb);
    }
    auth_withpeer_fail(pcb, PPP_EAP);
    return;
  }

  if (len <= 0) {
    ppp_error(("EAP: empty Request message discarded"));
    return;
  }

  GETCHAR(typenum, inp);
  len--;

  switch (typenum) {
  case EAPT_IDENTITY:
    if (len > 0)
      ppp_info(("EAP: Identity prompt \"%.*q\"", len, inp));
#ifdef USE_SRP
    if (pcb->eap.es_usepseudo &&
        (pcb->eap.es_usedpseudo == 0 ||
         (pcb->eap.es_usedpseudo == 1 && id == pcb->eap.es_client.ea_id))) {
      pcb->eap.es_usedpseudo = 1;
      /* Try to get a pseudonym */
      if ((fd = open_pn_file(O_RDONLY)) >= 0) {
        strcpy(rhostname, SRP_PSEUDO_ID);
        len = read(fd, rhostname + SRP_PSEUDO_LEN,
                   sizeof(rhostname) - SRP_PSEUDO_LEN);
        /* XXX NAI unsupported */
        if (len > 0) {
          eap_send_response(pcb, id, typenum, rhostname, len + SRP_PSEUDO_LEN);
        }
        (void)close(fd);
        if (len > 0)
          break;
      }
    }
    /* Stop using pseudonym now. */
    if (pcb->eap.es_usepseudo && pcb->eap.es_usedpseudo != 2) {
      remove_pn_file();
      pcb->eap.es_usedpseudo = 2;
    }
#endif /* USE_SRP */
    eap_send_response(pcb, id, typenum,
                      (const u_char *)pcb->eap.es_client.ea_name,
                      pcb->eap.es_client.ea_namelen);
    break;

  case EAPT_NOTIFICATION:
    if (len > 0)
      ppp_info(("EAP: Notification \"%.*q\"", len, inp));
    eap_send_response(pcb, id, typenum, NULL, 0);
    break;

  case EAPT_NAK:
    /*
     * Avoid the temptation to send Response Nak in reply
     * to Request Nak here.  It can only lead to trouble.
     */
    ppp_warn(("EAP: unexpected Nak in Request; ignored"));
    /* Return because we're waiting for something real. */
    return;

  case EAPT_MD5CHAP:
    if (len < 1) {
      ppp_error(("EAP: received MD5-Challenge with no data"));
      /* Bogus request; wait for something real. */
      return;
    }
    GETCHAR(vallen, inp);
    len--;
    if (vallen < 8 || vallen > len) {
      ppp_error(("EAP: MD5-Challenge with bad length %d (8..%d)", vallen, len));
      /* Try something better. */
      eap_send_nak(pcb, id, EAPT_SRP);
      break;
    }

    /* Not so likely to happen. */
    if (len - vallen >= (int)sizeof(rhostname)) {
      ppp_dbglog(("EAP: trimming really long peer name down"));
      MEMCPY(rhostname, inp + vallen, sizeof(rhostname) - 1);
      rhostname[sizeof(rhostname) - 1] = '\0';
    } else {
      MEMCPY(rhostname, inp + vallen, len - vallen);
      rhostname[len - vallen] = '\0';
    }

#if PPP_REMOTENAME
    /* In case the remote doesn't give us his name. */
    if (pcb->settings.explicit_remote ||
        (pcb->settings.remote_name[0] != '\0' && vallen == len))
      strlcpy(rhostname, pcb->settings.remote_name, sizeof(rhostname));
#endif /* PPP_REMOTENAME */

    /*
     * Get the secret for authenticating ourselves with
     * the specified host.
     */
    if (!get_secret(pcb, pcb->eap.es_client.ea_name, rhostname, secret,
                    &secret_len, 0)) {
      ppp_dbglog(("EAP: no MD5 secret for auth to %q", rhostname));
      eap_send_nak(pcb, id, EAPT_SRP);
      break;
    }
    lwip_md5_init(&mdContext);
    lwip_md5_starts(&mdContext);
    typenum = id;
    lwip_md5_update(&mdContext, &typenum, 1);
    lwip_md5_update(&mdContext, (u_char *)secret, secret_len);
    BZERO(secret, sizeof(secret));
    lwip_md5_update(&mdContext, inp, vallen);
    lwip_md5_finish(&mdContext, hash);
    lwip_md5_free(&mdContext);
    eap_chap_response(pcb, id, hash, pcb->eap.es_client.ea_name,
                      pcb->eap.es_client.ea_namelen);
    break;

#ifdef USE_SRP
  case EAPT_SRP:
    if (len < 1) {
      ppp_error(("EAP: received empty SRP Request"));
      /* Bogus request; wait for something real. */
      return;
    }

    /* Get subtype */
    GETCHAR(vallen, inp);
    len--;
    switch (vallen) {
    case EAPSRP_CHALLENGE:
      tc = NULL;
      if (pcb->eap.es_client.ea_session != NULL) {
        tc = (struct t_client *)pcb->eap.es_client.ea_session;
        /*
         * If this is a new challenge, then start
         * over with a new client session context.
         * Otherwise, just resend last response.
         */
        if (id != pcb->eap.es_client.ea_id) {
          t_clientclose(tc);
          pcb->eap.es_client.ea_session = NULL;
          tc = NULL;
        }
      }
      /* No session key just yet */
      pcb->eap.es_client.ea_skey = NULL;
      if (tc == NULL) {
        int rhostnamelen;

        GETCHAR(vallen, inp);
        len--;
        if (vallen >= len) {
          ppp_error(("EAP: badly-formed SRP Challenge"
                     " (name)"));
          /* Ignore badly-formed messages */
          return;
        }
        MEMCPY(rhostname, inp, vallen);
        rhostname[vallen] = '\0';
        INCPTR(vallen, inp);
        len -= vallen;

        /*
         * In case the remote doesn't give us his name,
         * use configured name.
         */
        if (explicit_remote || (remote_name[0] != '\0' && vallen == 0)) {
          strlcpy(rhostname, remote_name, sizeof(rhostname));
        }

        rhostnamelen = (int)strlen(rhostname);
        if (rhostnamelen > MAXNAMELEN) {
          rhostnamelen = MAXNAMELEN;
        }
        MEMCPY(pcb->eap.es_client.ea_peer, rhostname, rhostnamelen);
        pcb->eap.es_client.ea_peer[rhostnamelen] = '\0';
        pcb->eap.es_client.ea_peerlen = rhostnamelen;

        GETCHAR(vallen, inp);
        len--;
        if (vallen >= len) {
          ppp_error(("EAP: badly-formed SRP Challenge"
                     " (s)"));
          /* Ignore badly-formed messages */
          return;
        }
        sval.data = inp;
        sval.len = vallen;
        INCPTR(vallen, inp);
        len -= vallen;

        GETCHAR(vallen, inp);
        len--;
        if (vallen > len) {
          ppp_error(("EAP: badly-formed SRP Challenge"
                     " (g)"));
          /* Ignore badly-formed messages */
          return;
        }
        /* If no generator present, then use value 2 */
        if (vallen == 0) {
          gval.data = (u_char *)"\002";
          gval.len = 1;
        } else {
          gval.data = inp;
          gval.len = vallen;
        }
        INCPTR(vallen, inp);
        len -= vallen;

        /*
         * If no modulus present, then use well-known
         * value.
         */
        if (len == 0) {
          Nval.data = (u_char *)wkmodulus;
          Nval.len = sizeof(wkmodulus);
        } else {
          Nval.data = inp;
          Nval.len = len;
        }
        tc = t_clientopen(pcb->eap.es_client.ea_name, &Nval, &gval, &sval);
        if (tc == NULL) {
          eap_send_nak(pcb, id, EAPT_MD5CHAP);
          break;
        }
        pcb->eap.es_client.ea_session = (void *)tc;

        /* Add Challenge ID & type to verifier */
        vals[0] = id;
        vals[1] = EAPT_SRP;
        t_clientaddexdata(tc, vals, 2);
      }
      Ap = t_clientgenexp(tc);
      eap_srp_response(esp, id, EAPSRP_CKEY, Ap->data, Ap->len);
      break;

    case EAPSRP_SKEY:
      tc = (struct t_client *)pcb->eap.es_client.ea_session;
      if (tc == NULL) {
        ppp_warn(("EAP: peer sent Subtype 2 without 1"));
        eap_send_nak(pcb, id, EAPT_MD5CHAP);
        break;
      }
      if (pcb->eap.es_client.ea_skey != NULL) {
        /*
         * ID number should not change here.  Warn
         * if it does (but otherwise ignore).
         */
        if (id != pcb->eap.es_client.ea_id) {
          ppp_warn(("EAP: ID changed from %d to %d "
                    "in SRP Subtype 2 rexmit",
                    pcb->eap.es_client.ea_id, id));
        }
      } else {
        if (get_srp_secret(pcb->eap.es_unit, pcb->eap.es_client.ea_name,
                           pcb->eap.es_client.ea_peer, secret, 0) == 0) {
          /*
           * Can't work with this peer because
           * the secret is missing.  Just give
           * up.
           */
          eap_send_nak(pcb, id, EAPT_MD5CHAP);
          break;
        }
        Bval.data = inp;
        Bval.len = len;
        t_clientpasswd(tc, secret);
        BZERO(secret, sizeof(secret));
        pcb->eap.es_client.ea_skey = t_clientgetkey(tc, &Bval);
        if (pcb->eap.es_client.ea_skey == NULL) {
          /* Server is rogue; stop now */
          ppp_error(("EAP: SRP server is rogue"));
          goto client_failure;
        }
      }
      eap_srpval_response(esp, id, SRPVAL_EBIT, t_clientresponse(tc));
      break;

    case EAPSRP_SVALIDATOR:
      tc = (struct t_client *)pcb->eap.es_client.ea_session;
      if (tc == NULL || pcb->eap.es_client.ea_skey == NULL) {
        ppp_warn(("EAP: peer sent Subtype 3 without 1/2"));
        eap_send_nak(pcb, id, EAPT_MD5CHAP);
        break;
      }
      /*
       * If we're already open, then this ought to be a
       * duplicate.  Otherwise, check that the server is
       * who we think it is.
       */
      if (pcb->eap.es_client.ea_state == eapOpen) {
        if (id != pcb->eap.es_client.ea_id) {
          ppp_warn(("EAP: ID changed from %d to %d "
                    "in SRP Subtype 3 rexmit",
                    pcb->eap.es_client.ea_id, id));
        }
      } else {
        len -= sizeof(u32_t) + SHA_DIGESTSIZE;
        if (len < 0 || t_clientverify(tc, inp + sizeof(u32_t)) != 0) {
          ppp_error(("EAP: SRP server verification "
                     "failed"));
          goto client_failure;
        }
        GETLONG(pcb->eap.es_client.ea_keyflags, inp);
        /* Save pseudonym if user wants it. */
        if (len > 0 && pcb->eap.es_usepseudo) {
          INCPTR(SHA_DIGESTSIZE, inp);
          write_pseudonym(esp, inp, len, id);
        }
      }
      /*
       * We've verified our peer.  We're now mostly done,
       * except for waiting on the regular EAP Success
       * message.
       */
      eap_srp_response(esp, id, EAPSRP_ACK, NULL, 0);
      break;

    case EAPSRP_LWRECHALLENGE:
      if (len < 4) {
        ppp_warn(("EAP: malformed Lightweight rechallenge"));
        return;
      }
      SHA1Init(&ctxt);
      vals[0] = id;
      SHA1Update(&ctxt, vals, 1);
      SHA1Update(&ctxt, pcb->eap.es_client.ea_skey, SESSION_KEY_LEN);
      SHA1Update(&ctxt, inp, len);
      SHA1Update(&ctxt, pcb->eap.es_client.ea_name,
                 pcb->eap.es_client.ea_namelen);
      SHA1Final(dig, &ctxt);
      eap_srp_response(esp, id, EAPSRP_LWRECHALLENGE, dig, SHA_DIGESTSIZE);
      break;

    default:
      ppp_error(("EAP: unknown SRP Subtype %d", vallen));
      eap_send_nak(pcb, id, EAPT_MD5CHAP);
      break;
    }
    break;
#endif /* USE_SRP */

  default:
    ppp_info(("EAP: unknown authentication type %d; Naking", typenum));
    eap_send_nak(pcb, id, EAPT_SRP);
    break;
  }

  if (pcb->settings.eap_req_time > 0) {
    UNTIMEOUT(eap_client_timeout, pcb);
    TIMEOUT(eap_client_timeout, pcb, pcb->settings.eap_req_time);
  }
  return;

#ifdef USE_SRP
client_failure:
  pcb->eap.es_client.ea_state = eapBadAuth;
  if (pcb->settings.eap_req_time > 0) {
    UNTIMEOUT(eap_client_timeout, (void *)esp);
  }
  pcb->eap.es_client.ea_session = NULL;
  t_clientclose(tc);
  auth_withpeer_fail(pcb, PPP_EAP);
#endif /* USE_SRP */
}

#if PPP_SERVER
/*
 * eap_response - Receive EAP Response message (server mode).
 */
static void eap_response(ppp_pcb *pcb, u_char *inp, int id, int len) {
  u_char typenum;
  u_char vallen;
  int secret_len;
  char secret[MAXSECRETLEN];
  char rhostname[MAXNAMELEN];
  lwip_md5_context mdContext;
  u_char hash[MD5_SIGNATURE_SIZE];
#ifdef USE_SRP
  struct t_server *ts;
  struct t_num A;
  SHA1_CTX ctxt;
  u_char dig[SHA_DIGESTSIZE];
#endif /* USE_SRP */

  /*
   * Ignore responses if we're not open
   */
  if (pcb->eap.es_server.ea_state <= eapClosed)
    return;

  if (pcb->eap.es_server.ea_id != id) {
    ppp_dbglog(("EAP: discarding Response %d; expected ID %d", id,
                pcb->eap.es_server.ea_id));
    return;
  }

  pcb->eap.es_server.ea_responses++;

  if (len <= 0) {
    ppp_error(("EAP: empty Response message discarded"));
    return;
  }

  GETCHAR(typenum, inp);
  len--;

  switch (typenum) {
  case EAPT_IDENTITY:
    if (pcb->eap.es_server.ea_state != eapIdentify) {
      ppp_dbglog(("EAP discarding unwanted Identify \"%.q\"", len, inp));
      break;
    }
    ppp_info(("EAP: unauthenticated peer name \"%.*q\"", len, inp));
    if (len > MAXNAMELEN) {
      len = MAXNAMELEN;
    }
    MEMCPY(pcb->eap.es_server.ea_peer, inp, len);
    pcb->eap.es_server.ea_peer[len] = '\0';
    pcb->eap.es_server.ea_peerlen = len;
    eap_figure_next_state(pcb, 0);
    break;

  case EAPT_NOTIFICATION:
    ppp_dbglog(("EAP unexpected Notification; response discarded"));
    break;

  case EAPT_NAK:
    if (len < 1) {
      ppp_info(("EAP: Nak Response with no suggested protocol"));
      eap_figure_next_state(pcb, 1);
      break;
    }

    GETCHAR(vallen, inp);
    len--;

    if (
#if PPP_REMOTENAME
        !pcb->explicit_remote &&
#endif /* PPP_REMOTENAME */
        pcb->eap.es_server.ea_state == eapIdentify) {
      /* Peer cannot Nak Identify Request */
      eap_figure_next_state(pcb, 1);
      break;
    }

    switch (vallen) {
    case EAPT_SRP:
      /* Run through SRP validator selection again. */
      pcb->eap.es_server.ea_state = eapIdentify;
      eap_figure_next_state(pcb, 0);
      break;

    case EAPT_MD5CHAP:
      pcb->eap.es_server.ea_state = eapMD5Chall;
      break;

    default:
      ppp_dbglog(("EAP: peer requesting unknown Type %d", vallen));
      switch (pcb->eap.es_server.ea_state) {
      case eapSRP1:
      case eapSRP2:
      case eapSRP3:
        pcb->eap.es_server.ea_state = eapMD5Chall;
        break;
      case eapMD5Chall:
      case eapSRP4:
        pcb->eap.es_server.ea_state = eapIdentify;
        eap_figure_next_state(pcb, 0);
        break;
      default:
        break;
      }
      break;
    }
    break;

  case EAPT_MD5CHAP:
    if (pcb->eap.es_server.ea_state != eapMD5Chall) {
      ppp_error(("EAP: unexpected MD5-Response"));
      eap_figure_next_state(pcb, 1);
      break;
    }
    if (len < 1) {
      ppp_error(("EAP: received MD5-Response with no data"));
      eap_figure_next_state(pcb, 1);
      break;
    }
    GETCHAR(vallen, inp);
    len--;
    if (vallen != 16 || vallen > len) {
      ppp_error(("EAP: MD5-Response with bad length %d", vallen));
      eap_figure_next_state(pcb, 1);
      break;
    }

    /* Not so likely to happen. */
    if (len - vallen >= (int)sizeof(rhostname)) {
      ppp_dbglog(("EAP: trimming really long peer name down"));
      MEMCPY(rhostname, inp + vallen, sizeof(rhostname) - 1);
      rhostname[sizeof(rhostname) - 1] = '\0';
    } else {
      MEMCPY(rhostname, inp + vallen, len - vallen);
      rhostname[len - vallen] = '\0';
    }

#if PPP_REMOTENAME
    /* In case the remote doesn't give us his name. */
    if (explicit_remote || (remote_name[0] != '\0' && vallen == len))
      strlcpy(rhostname, remote_name, sizeof(rhostname));
#endif /* PPP_REMOTENAME */

    /*
     * Get the secret for authenticating the specified
     * host.
     */
    if (!get_secret(pcb, rhostname, pcb->eap.es_server.ea_name, secret,
                    &secret_len, 1)) {
      ppp_dbglog(("EAP: no MD5 secret for auth of %q", rhostname));
      eap_send_failure(pcb);
      break;
    }
    lwip_md5_init(&mdContext);
    lwip_md5_starts(&mdContext);
    lwip_md5_update(&mdContext, &pcb->eap.es_server.ea_id, 1);
    lwip_md5_update(&mdContext, (u_char *)secret, secret_len);
    BZERO(secret, sizeof(secret));
    lwip_md5_update(&mdContext, pcb->eap.es_challenge, pcb->eap.es_challen);
    lwip_md5_finish(&mdContext, hash);
    lwip_md5_free(&mdContext);
    if (BCMP(hash, inp, MD5_SIGNATURE_SIZE) != 0) {
      eap_send_failure(pcb);
      break;
    }
    pcb->eap.es_server.ea_type = EAPT_MD5CHAP;
    eap_send_success(pcb);
    eap_figure_next_state(pcb, 0);
    if (pcb->eap.es_rechallenge != 0)
      TIMEOUT(eap_rechallenge, pcb, pcb->eap.es_rechallenge);
    break;

#ifdef USE_SRP
  case EAPT_SRP:
    if (len < 1) {
      ppp_error(("EAP: empty SRP Response"));
      eap_figure_next_state(pcb, 1);
      break;
    }
    GETCHAR(typenum, inp);
    len--;
    switch (typenum) {
    case EAPSRP_CKEY:
      if (pcb->eap.es_server.ea_state != eapSRP1) {
        ppp_error(("EAP: unexpected SRP Subtype 1 Response"));
        eap_figure_next_state(pcb, 1);
        break;
      }
      A.data = inp;
      A.len = len;
      ts = (struct t_server *)pcb->eap.es_server.ea_session;
      assert(ts != NULL);
      pcb->eap.es_server.ea_skey = t_servergetkey(ts, &A);
      if (pcb->eap.es_server.ea_skey == NULL) {
        /* Client's A value is bogus; terminate now */
        ppp_error(("EAP: bogus A value from client"));
        eap_send_failure(pcb);
      } else {
        eap_figure_next_state(pcb, 0);
      }
      break;

    case EAPSRP_CVALIDATOR:
      if (pcb->eap.es_server.ea_state != eapSRP2) {
        ppp_error(("EAP: unexpected SRP Subtype 2 Response"));
        eap_figure_next_state(pcb, 1);
        break;
      }
      if (len < sizeof(u32_t) + SHA_DIGESTSIZE) {
        ppp_error(
            ("EAP: M1 length %d < %d", len, sizeof(u32_t) + SHA_DIGESTSIZE));
        eap_figure_next_state(pcb, 1);
        break;
      }
      GETLONG(pcb->eap.es_server.ea_keyflags, inp);
      ts = (struct t_server *)pcb->eap.es_server.ea_session;
      assert(ts != NULL);
      if (t_serververify(ts, inp)) {
        ppp_info(("EAP: unable to validate client identity"));
        eap_send_failure(pcb);
        break;
      }
      eap_figure_next_state(pcb, 0);
      break;

    case EAPSRP_ACK:
      if (pcb->eap.es_server.ea_state != eapSRP3) {
        ppp_error(("EAP: unexpected SRP Subtype 3 Response"));
        eap_send_failure(esp);
        break;
      }
      pcb->eap.es_server.ea_type = EAPT_SRP;
      eap_send_success(pcb, esp);
      eap_figure_next_state(pcb, 0);
      if (pcb->eap.es_rechallenge != 0)
        TIMEOUT(eap_rechallenge, pcb, pcb->eap.es_rechallenge);
      if (pcb->eap.es_lwrechallenge != 0)
        TIMEOUT(srp_lwrechallenge, pcb, pcb->eap.es_lwrechallenge);
      break;

    case EAPSRP_LWRECHALLENGE:
      if (pcb->eap.es_server.ea_state != eapSRP4) {
        ppp_info(("EAP: unexpected SRP Subtype 4 Response"));
        return;
      }
      if (len != SHA_DIGESTSIZE) {
        ppp_error(("EAP: bad Lightweight rechallenge "
                   "response"));
        return;
      }
      SHA1Init(&ctxt);
      vallen = id;
      SHA1Update(&ctxt, &vallen, 1);
      SHA1Update(&ctxt, pcb->eap.es_server.ea_skey, SESSION_KEY_LEN);
      SHA1Update(&ctxt, pcb->eap.es_challenge, pcb->eap.es_challen);
      SHA1Update(&ctxt, pcb->eap.es_server.ea_peer,
                 pcb->eap.es_server.ea_peerlen);
      SHA1Final(dig, &ctxt);
      if (BCMP(dig, inp, SHA_DIGESTSIZE) != 0) {
        ppp_error(("EAP: failed Lightweight rechallenge"));
        eap_send_failure(pcb);
        break;
      }
      pcb->eap.es_server.ea_state = eapOpen;
      if (pcb->eap.es_lwrechallenge != 0)
        TIMEOUT(srp_lwrechallenge, esp, pcb->eap.es_lwrechallenge);
      break;
    }
    break;
#endif /* USE_SRP */

  default:
    /* This can't happen. */
    ppp_error(("EAP: unknown Response type %d; ignored", typenum));
    return;
  }

  if (pcb->settings.eap_timeout_time > 0) {
    UNTIMEOUT(eap_server_timeout, pcb);
  }

  if (pcb->eap.es_server.ea_state != eapBadAuth &&
      pcb->eap.es_server.ea_state != eapOpen) {
    pcb->eap.es_server.ea_id++;
    eap_send_request(pcb);
  }
}
#endif /* PPP_SERVER */

/*
 * eap_success - Receive EAP Success message (client mode).
 */
static void eap_success(ppp_pcb *pcb, u_char *inp, int id, int len) {
  LWIP_UNUSED_ARG(id);

  if (pcb->eap.es_client.ea_state != eapOpen && !eap_client_active(pcb)) {
    ppp_dbglog(("EAP unexpected success message in state %s (%d)",
                eap_state_name(pcb->eap.es_client.ea_state),
                pcb->eap.es_client.ea_state));
    return;
  }

  if (pcb->settings.eap_req_time > 0) {
    UNTIMEOUT(eap_client_timeout, pcb);
  }

  if (len > 0) {
    /* This is odd.  The spec doesn't allow for this. */
    PRINTMSG(inp, len);
  }

  pcb->eap.es_client.ea_state = eapOpen;
  auth_withpeer_success(pcb, PPP_EAP, 0);
}

/*
 * eap_failure - Receive EAP Failure message (client mode).
 */
static void eap_failure(ppp_pcb *pcb, u_char *inp, int id, int len) {
  LWIP_UNUSED_ARG(id);

  /*
   * Ignore failure messages if we're not open
   */
  if (pcb->eap.es_client.ea_state <= eapClosed)
    return;

  if (!eap_client_active(pcb)) {
    ppp_dbglog(("EAP unexpected failure message in state %s (%d)",
                eap_state_name(pcb->eap.es_client.ea_state),
                pcb->eap.es_client.ea_state));
  }

  if (pcb->settings.eap_req_time > 0) {
    UNTIMEOUT(eap_client_timeout, pcb);
  }

  if (len > 0) {
    /* This is odd.  The spec doesn't allow for this. */
    PRINTMSG(inp, len);
  }

  pcb->eap.es_client.ea_state = eapBadAuth;

  ppp_error(("EAP: peer reports authentication failure"));
  auth_withpeer_fail(pcb, PPP_EAP);
}

/*
 * eap_input - Handle received EAP message.
 */
static void eap_input(ppp_pcb *pcb, u_char *inp, int inlen) {
  u_char code, id;
  int len;

  /*
   * Parse header (code, id and length).  If packet too short,
   * drop it.
   */
  if (inlen < EAP_HEADERLEN) {
    ppp_error(("EAP: packet too short: %d < %d", inlen, EAP_HEADERLEN));
    return;
  }
  GETCHAR(code, inp);
  GETCHAR(id, inp);
  GETSHORT(len, inp);
  if (len < EAP_HEADERLEN || len > inlen) {
    ppp_error(("EAP: packet has illegal length field %d (%d..%d)", len,
               EAP_HEADERLEN, inlen));
    return;
  }
  len -= EAP_HEADERLEN;

  /* Dispatch based on message code */
  switch (code) {
  case EAP_REQUEST:
    eap_request(pcb, inp, id, len);
    break;

#if PPP_SERVER
  case EAP_RESPONSE:
    eap_response(pcb, inp, id, len);
    break;
#endif /* PPP_SERVER */

  case EAP_SUCCESS:
    eap_success(pcb, inp, id, len);
    break;

  case EAP_FAILURE:
    eap_failure(pcb, inp, id, len);
    break;

  default: /* XXX Need code reject */
    /* Note: it's not legal to send EAP Nak here. */
    ppp_warn(("EAP: unknown code %d received", code));
    break;
  }
}

#if PRINTPKT_SUPPORT
/*
 * eap_printpkt - print the contents of an EAP packet.
 */
static const char *const eap_codenames[] = {"Request", "Response", "Success",
                                            "Failure"};

static const char *const eap_typenames[] = {
    "Identity", "Notification",  "Nak",          "MD5-Challenge",
    "OTP",      "Generic-Token", NULL,           NULL,
    "RSA",      "DSS",           "KEA",          "KEA-Validate",
    "TLS",      "Defender",      "Windows 2000", "Arcot",
    "Cisco",    "Nokia",         "SRP"};

static int eap_printpkt(const u_char *inp, int inlen,
                        void (*printer)(void *, const char *, ...), void *arg) {
  int code, id, len, rtype, vallen;
  const u_char *pstart;
  u32_t uval;

  if (inlen < EAP_HEADERLEN)
    return (0);
  pstart = inp;
  GETCHAR(code, inp);
  GETCHAR(id, inp);
  GETSHORT(len, inp);
  if (len < EAP_HEADERLEN || len > inlen)
    return (0);

  if (code >= 1 && code <= (int)LWIP_ARRAYSIZE(eap_codenames))
    printer(arg, " %s", eap_codenames[code - 1]);
  else
    printer(arg, " code=0x%x", code);
  printer(arg, " id=0x%x", id);
  len -= EAP_HEADERLEN;
  switch (code) {
  case EAP_REQUEST:
    if (len < 1) {
      printer(arg, " <missing type>");
      break;
    }
    GETCHAR(rtype, inp);
    len--;
    if (rtype >= 1 && rtype <= (int)LWIP_ARRAYSIZE(eap_typenames))
      printer(arg, " %s", eap_typenames[rtype - 1]);
    else
      printer(arg, " type=0x%x", rtype);
    switch (rtype) {
    case EAPT_IDENTITY:
    case EAPT_NOTIFICATION:
      if (len > 0) {
        printer(arg, " <Message ");
        ppp_print_string(inp, len, printer, arg);
        printer(arg, ">");
        INCPTR(len, inp);
        len = 0;
      } else {
        printer(arg, " <No message>");
      }
      break;

    case EAPT_MD5CHAP:
      if (len <= 0)
        break;
      GETCHAR(vallen, inp);
      len--;
      if (vallen > len)
        goto truncated;
      printer(arg, " <Value%.*B>", vallen, inp);
      INCPTR(vallen, inp);
      len -= vallen;
      if (len > 0) {
        printer(arg, " <Name ");
        ppp_print_string(inp, len, printer, arg);
        printer(arg, ">");
        INCPTR(len, inp);
        len = 0;
      } else {
        printer(arg, " <No name>");
      }
      break;

    case EAPT_SRP:
      if (len < 3)
        goto truncated;
      GETCHAR(vallen, inp);
      len--;
      printer(arg, "-%d", vallen);
      switch (vallen) {
      case EAPSRP_CHALLENGE:
        GETCHAR(vallen, inp);
        len--;
        if (vallen >= len)
          goto truncated;
        if (vallen > 0) {
          printer(arg, " <Name ");
          ppp_print_string(inp, vallen, printer, arg);
          printer(arg, ">");
        } else {
          printer(arg, " <No name>");
        }
        INCPTR(vallen, inp);
        len -= vallen;
        GETCHAR(vallen, inp);
        len--;
        if (vallen >= len)
          goto truncated;
        printer(arg, " <s%.*B>", vallen, inp);
        INCPTR(vallen, inp);
        len -= vallen;
        GETCHAR(vallen, inp);
        len--;
        if (vallen > len)
          goto truncated;
        if (vallen == 0) {
          printer(arg, " <Default g=2>");
        } else {
          printer(arg, " <g%.*B>", vallen, inp);
        }
        INCPTR(vallen, inp);
        len -= vallen;
        if (len == 0) {
          printer(arg, " <Default N>");
        } else {
          printer(arg, " <N%.*B>", len, inp);
          INCPTR(len, inp);
          len = 0;
        }
        break;

      case EAPSRP_SKEY:
        printer(arg, " <B%.*B>", len, inp);
        INCPTR(len, inp);
        len = 0;
        break;

      case EAPSRP_SVALIDATOR:
        if (len < (int)sizeof(u32_t))
          break;
        GETLONG(uval, inp);
        len -= sizeof(u32_t);
        if (uval & SRPVAL_EBIT) {
          printer(arg, " E");
          uval &= ~SRPVAL_EBIT;
        }
        if (uval != 0) {
          printer(arg, " f<%X>", uval);
        }
        if ((vallen = len) > SHA_DIGESTSIZE)
          vallen = SHA_DIGESTSIZE;
        printer(arg, " <M2%.*B%s>", len, inp, len < SHA_DIGESTSIZE ? "?" : "");
        INCPTR(vallen, inp);
        len -= vallen;
        if (len > 0) {
          printer(arg, " <PN%.*B>", len, inp);
          INCPTR(len, inp);
          len = 0;
        }
        break;

      case EAPSRP_LWRECHALLENGE:
        printer(arg, " <Challenge%.*B>", len, inp);
        INCPTR(len, inp);
        len = 0;
        break;
      default:
        break;
      }
      break;
    default:
      break;
    }
    break;

  case EAP_RESPONSE:
    if (len < 1)
      break;
    GETCHAR(rtype, inp);
    len--;
    if (rtype >= 1 && rtype <= (int)LWIP_ARRAYSIZE(eap_typenames))
      printer(arg, " %s", eap_typenames[rtype - 1]);
    else
      printer(arg, " type=0x%x", rtype);
    switch (rtype) {
    case EAPT_IDENTITY:
      if (len > 0) {
        printer(arg, " <Name ");
        ppp_print_string(inp, len, printer, arg);
        printer(arg, ">");
        INCPTR(len, inp);
        len = 0;
      }
      break;

    case EAPT_NAK:
      if (len <= 0) {
        printer(arg, " <missing hint>");
        break;
      }
      GETCHAR(rtype, inp);
      len--;
      printer(arg, " <Suggested-type %02X", rtype);
      if (rtype >= 1 && rtype < (int)LWIP_ARRAYSIZE(eap_typenames))
        printer(arg, " (%s)", eap_typenames[rtype - 1]);
      printer(arg, ">");
      break;

    case EAPT_MD5CHAP:
      if (len <= 0) {
        printer(arg, " <missing length>");
        break;
      }
      GETCHAR(vallen, inp);
      len--;
      if (vallen > len)
        goto truncated;
      printer(arg, " <Value%.*B>", vallen, inp);
      INCPTR(vallen, inp);
      len -= vallen;
      if (len > 0) {
        printer(arg, " <Name ");
        ppp_print_string(inp, len, printer, arg);
        printer(arg, ">");
        INCPTR(len, inp);
        len = 0;
      } else {
        printer(arg, " <No name>");
      }
      break;

    case EAPT_SRP:
      if (len < 1)
        goto truncated;
      GETCHAR(vallen, inp);
      len--;
      printer(arg, "-%d", vallen);
      switch (vallen) {
      case EAPSRP_CKEY:
        printer(arg, " <A%.*B>", len, inp);
        INCPTR(len, inp);
        len = 0;
        break;

      case EAPSRP_CVALIDATOR:
        if (len < (int)sizeof(u32_t))
          break;
        GETLONG(uval, inp);
        len -= sizeof(u32_t);
        if (uval & SRPVAL_EBIT) {
          printer(arg, " E");
          uval &= ~SRPVAL_EBIT;
        }
        if (uval != 0) {
          printer(arg, " f<%X>", uval);
        }
        printer(arg, " <M1%.*B%s>", len, inp, len == SHA_DIGESTSIZE ? "" : "?");
        INCPTR(len, inp);
        len = 0;
        break;

      case EAPSRP_ACK:
        break;

      case EAPSRP_LWRECHALLENGE:
        printer(arg, " <Response%.*B%s>", len, inp,
                len == SHA_DIGESTSIZE ? "" : "?");
        if ((vallen = len) > SHA_DIGESTSIZE)
          vallen = SHA_DIGESTSIZE;
        INCPTR(vallen, inp);
        len -= vallen;
        break;
      default:
        break;
      }
      break;
    default:
      break;
    }
    break;

  case EAP_SUCCESS: /* No payload expected for these! */
  case EAP_FAILURE:
  default:
    break;

  truncated:
    printer(arg, " <truncated>");
    break;
  }

  if (len > 8)
    printer(arg, "%8B...", inp);
  else if (len > 0)
    printer(arg, "%.*B", len, inp);
  INCPTR(len, inp);

  return (inp - pstart);
}
#endif /* PRINTPKT_SUPPORT */

#endif /* PPP_SUPPORT && EAP_SUPPORT */
